# ======================================================================
# Copyright CERFACS (February 2019)
# Contributor: Adrien Suau (adrien.suau@cerfacs.fr)
#
# This software is governed by the CeCILL-B license under French law and
# abiding  by the  rules of  distribution of free software. You can use,
# modify  and/or  redistribute  the  software  under  the  terms  of the
# CeCILL-B license as circulated by CEA, CNRS and INRIA at the following
# URL "http://www.cecill.info".
#
# As a counterpart to the access to  the source code and rights to copy,
# modify and  redistribute granted  by the  license, users  are provided
# only with a limited warranty and  the software's author, the holder of
# the economic rights,  and the  successive licensors  have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using, modifying and/or  developing or reproducing  the
# software by the user in light of its specific status of free software,
# that  may mean  that it  is complicated  to manipulate,  and that also
# therefore  means that  it is reserved for  developers and  experienced
# professionals having in-depth  computer knowledge. Users are therefore
# encouraged  to load and  test  the software's  suitability as  regards
# their  requirements  in  conditions  enabling  the  security  of their
# systems  and/or  data to be  ensured and,  more generally,  to use and
# operate it in the same conditions as regards security.
#
# The fact that you  are presently reading this  means that you have had
# knowledge of the CeCILL-B license and that you accept its terms.
# ======================================================================

import typing as ty

from qat.lang.AQASM.routines import QRoutine
from qat.lang.AQASM.gates import ParamGate
from qat.lang.AQASM.misc import build_gate


def simulate_using_trotter(
    order: int, time: float, generators: ty.List[ty.Callable[[float], QRoutine]]
) -> ParamGate:
    """Simulate the Hamiltonian represented by the given generators.

    :param order: Order of the Trotter formula to use.
    :param time: Time of simulation.
    :param generators: A list of functions. Each function simulates the hamiltonian H_j
        from the sum decomposition for a time given in parameter.
    """
    if not isinstance(generators, list):
        generators = list(generators)

    # Warning with this. See commit message, but the hack might lead to hard-to-debug
    # bugs if different values of the generators parameters are used in one run.
    @build_gate("simulate_using_trotter", [int, float], arity=generators[0](0).arity)
    def _internal_simulate_using_trotter(order: int, time: float) -> QRoutine:
        procedure = QRoutine()
        # Base case, when the order is 1.
        if order == 1:
            for generator in generators[:-1]:
                gate = generator(time / 2)
                procedure.apply(gate, list(range(gate.arity)))

            middle_gate = generators[-1](time)
            procedure.apply(middle_gate, list(range(middle_gate.arity)))

            for generator in reversed(generators[:-1]):
                gate = generator(time / 2)
                procedure.apply(gate, list(range(gate.arity)))
            return procedure

        # If the order is greater, then recurse!
        p_k = 1 / (4 - pow(4, 1 / (2 * order - 1)))
        recursion1 = simulate_using_trotter(order - 1, p_k * time, generators)
        recursion2 = simulate_using_trotter(order - 1, (1 - 4 * p_k) * time, generators)

        procedure.apply(recursion1, list(range(recursion1.arity)))
        procedure.apply(recursion1, list(range(recursion1.arity)))
        procedure.apply(recursion2, list(range(recursion2.arity)))
        procedure.apply(recursion1, list(range(recursion1.arity)))
        procedure.apply(recursion1, list(range(recursion1.arity)))

        return procedure

    return _internal_simulate_using_trotter(order, time)
